//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Graphics/GLshell/StateManager.h"

namespace Engine
{
namespace Graphics
{
	
	struct gl_wrap
	{
		enum type
		{
			CLAMP			= gles::GL_CLAMP_TO_EDGE,
			MIRRORED_REPEAT	= gles::GL_MIRRORED_REPEAT,
			REPEAT			= gles::GL_REPEAT,
		};
	};


	struct gl_filter
	{
		enum type
		{
			// min & mag
			NEAREST					= gles::GL_NEAREST,
			LINEAR					= gles::GL_LINEAR,
			// min only
			NEAREST_MIPMAP_NEAREST	= gles::GL_NEAREST_MIPMAP_NEAREST,
			LINEAR_MIPMAP_NEAREST	= gles::GL_LINEAR_MIPMAP_NEAREST,
			NEAREST_MIPMAP_LINEAR	= gles::GL_NEAREST_MIPMAP_LINEAR,
			LINEAR_MIPMAP_LINEAR	= gles::GL_LINEAR_MIPMAP_LINEAR,
		};
	};
	

	struct gl_texture
	{
		enum type
		{
			TEXTURE_2D			= gles::GL_TEXTURE_2D,
			TEXTURE_3D			= gles::OES_texture_3D::GL_TEXTURE_3D,
			CUBEMAP				= gles::GL_TEXTURE_CUBE_MAP,

			// for creating CLimage //
			CUBEFACE_POS_X		= gles::GL_TEXTURE_CUBE_MAP_POSITIVE_X,
			CUBEFACE_NEG_X		= gles::GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
			CUBEFACE_POS_Y		= gles::GL_TEXTURE_CUBE_MAP_POSITIVE_Y,
			CUBEFACE_NEG_Y		= gles::GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
			CUBEFACE_POS_Z		= gles::GL_TEXTURE_CUBE_MAP_POSITIVE_Z,
			CUBEFACE_NEG_Z		= gles::GL_TEXTURE_CUBE_MAP_NEGATIVE_Z,
			CUBEFACE_FIRST		= CUBEFACE_POS_X,
			CUBEFACE_LAST		= CUBEFACE_NEG_Z,
		};
	};


	//
	// Sampler
	//

	struct Sampler
	{
	// types
	public:
		struct Wrap
		{
			gl_wrap::type	s, t, r;
			///
			Wrap(gl_wrap::type s = gl_wrap::CLAMP,
				 gl_wrap::type t = gl_wrap::CLAMP,
				 gl_wrap::type r = gl_wrap::CLAMP):
				s(s), t(t), r(r) {}
			
			bool operator == (const Wrap &right) const {
				return s == right.s && t == right.t && r == right.r;
			}

			bool operator != (const Wrap &right) const {
				return not (*this == right);
			}
		};

		struct Filter {
			gl_filter::type	min,
							mag;
			///
			Filter(gl_filter::type min = gl_filter::LINEAR,
				   gl_filter::type mag = gl_filter::LINEAR):
				min(min), mag(mag) {}

			bool operator == (const Filter &right) const {
				return min == right.min && mag == right.mag;
			}

			bool operator != (const Filter &right) const {
				return not (*this == right);
			}
		};


	// variables
	public:
		Wrap				wrap;			// OES_texture_3D
		Filter				filter;
		gl_compare::type	compareRTT;		// EXT_shadow_samplers
		uint8				anisotropy;		// EXT_texture_filter_anisotropic	// TODO: change to float ???
		

	// methods
	public:
		Sampler () : compareRTT(gl_compare::NONE), anisotropy(1)
		{}

		Sampler (const Wrap &wrap, const Filter &filter, gl_compare::type cmp, uint8 anisotropy):
			wrap(wrap), filter(filter), compareRTT(cmp), anisotropy(anisotropy) {}

		bool operator == (const Sampler &other) const
		{
			return ( wrap == other.wrap && filter == other.filter &&
					 compareRTT == other.compareRTT && anisotropy == other.anisotropy );
		}

		void Configure (bool mipmaps, bool tilingX, bool tilingY, bool tilingZ = false, bool shadow = false)
		{
			wrap.s = tilingX ? gl_wrap::REPEAT : gl_wrap::CLAMP;
			wrap.t = tilingY ? gl_wrap::REPEAT : gl_wrap::CLAMP;
			wrap.r = tilingZ ? gl_wrap::REPEAT : gl_wrap::CLAMP;

			if ( mipmaps )
			{
				anisotropy = (uint8) gles::EXT_texture_filter_anisotropic::C_GL_MAX_TEXTURE_MAX_ANISOTROPY;
				filter.min = gl_filter::LINEAR_MIPMAP_LINEAR;
				filter.mag = gl_filter::LINEAR;
			}
			else
			{
				anisotropy = 1;
				filter.min = gl_filter::LINEAR;
				filter.mag = gl_filter::LINEAR;
			}

			compareRTT = shadow ? gl_compare::LEQUAL : gl_compare::NONE;
		}

		void Bind (gl_texture::type target) const
		{
			using namespace gles;

			GL_CALL( glTexParameteri( target, GL_TEXTURE_WRAP_S, wrap.s ) );
			GL_CALL( glTexParameteri( target, GL_TEXTURE_WRAP_T, wrap.t ) );

			if ( target == gl_texture::TEXTURE_3D && OES_texture_3D::IsSupported() )
				GL_CALL( glTexParameteri( target, OES_texture_3D::GL_TEXTURE_WRAP_R, wrap.r ) );

			GL_CALL( glTexParameteri( target, GL_TEXTURE_MIN_FILTER, filter.min ) );
			GL_CALL( glTexParameteri( target, GL_TEXTURE_MAG_FILTER, filter.mag ) );
		
			if ( EXT_shadow_samplers::IsSupported() )
			{
				GL_CALL( glTexParameteri( target, EXT_shadow_samplers::GL_TEXTURE_COMPARE_MODE,
										  compareRTT == gl_compare::NONE ? GL_NONE :
										  (GLenum)EXT_shadow_samplers::GL_COMPARE_REF_TO_TEXTURE ) );
			
				if ( compareRTT != gl_compare::NONE )
					GL_CALL( glTexParameteri( target, EXT_shadow_samplers::GL_TEXTURE_COMPARE_FUNC, compareRTT ) );
			}

			if ( EXT_texture_filter_anisotropic::IsSupported() )
			{
				float a = Clamp( (float)anisotropy, 1.0f, EXT_texture_filter_anisotropic::C_GL_MAX_TEXTURE_MAX_ANISOTROPY );
				GL_CALL( glTexParameterf( target, EXT_texture_filter_anisotropic::GL_TEXTURE_MAX_ANISOTROPY, a ) );
			}
		}

		void SetAndBind (gl_texture::type target, const Sampler &samp)
		{
			using namespace gles;
		
			if ( this->wrap != samp.wrap )
			{
				this->wrap = samp.wrap;

				GL_CALL( glTexParameteri( target, GL_TEXTURE_WRAP_S, wrap.s ) );
				GL_CALL( glTexParameteri( target, GL_TEXTURE_WRAP_T, wrap.t ) );

				if ( target == gl_texture::TEXTURE_3D && OES_texture_3D::IsSupported() )
					GL_CALL( glTexParameteri( target, OES_texture_3D::GL_TEXTURE_WRAP_R, wrap.r ) );
			}

			if ( this->filter != samp.filter )
			{
				this->filter = samp.filter;

				GL_CALL( glTexParameteri( target, GL_TEXTURE_MIN_FILTER, filter.min ) );
				GL_CALL( glTexParameteri( target, GL_TEXTURE_MAG_FILTER, filter.mag ) );
			}
		
			if ( EXT_shadow_samplers::IsSupported() and
				 this->compareRTT != samp.compareRTT )
			{
				this->compareRTT = samp.compareRTT;

				GL_CALL( glTexParameteri( target, EXT_shadow_samplers::GL_TEXTURE_COMPARE_MODE,
										  compareRTT == gl_compare::NONE ? GL_NONE :
										  (GLenum)EXT_shadow_samplers::GL_COMPARE_REF_TO_TEXTURE ) );
			
				if ( compareRTT != gl_compare::NONE )
					GL_CALL( glTexParameteri( target, EXT_shadow_samplers::GL_TEXTURE_COMPARE_FUNC, compareRTT ) );
			}

			if ( EXT_texture_filter_anisotropic::IsSupported() and
				 this->anisotropy != samp.anisotropy )
			{
				this->anisotropy = samp.anisotropy;
				float a = Clamp( (float)anisotropy, 1.0f, EXT_texture_filter_anisotropic::C_GL_MAX_TEXTURE_MAX_ANISOTROPY );
				GL_CALL( glTexParameterf( target, EXT_texture_filter_anisotropic::GL_TEXTURE_MAX_ANISOTROPY, a ) );
			}
		}

		bool NeedMipmaps() const
		{
			return	filter.min == gl_filter::LINEAR_MIPMAP_LINEAR or
					filter.min == gl_filter::LINEAR_MIPMAP_NEAREST or
					filter.min == gl_filter::NEAREST_MIPMAP_LINEAR or
					filter.min == gl_filter::NEAREST_MIPMAP_NEAREST or
					(gles::EXT_texture_filter_anisotropic::IsSupported() and anisotropy > 1);
		}

		bool SupportRenderToTexture () const
		{
			return	wrap.s == gl_wrap::CLAMP and
					wrap.t == gl_wrap::CLAMP and
					wrap.r == gl_wrap::CLAMP;
		}


		static Sampler	DefaultForRenderTarget ()
		{
			return Default2D( gl_wrap::CLAMP );
		}

		static Sampler  Default2D (gl_wrap::type wrap = gl_wrap::CLAMP)
		{
			return Sampler( Wrap( wrap, wrap, wrap ), Filter( gl_filter::LINEAR, gl_filter::LINEAR ), gl_compare::NONE, 0 );
		}

		static Sampler  Default3D (gl_wrap::type wrap = gl_wrap::CLAMP)
		{
			return Sampler( Wrap( wrap, wrap, wrap ), Filter( gl_filter::LINEAR_MIPMAP_LINEAR, gl_filter::LINEAR ),
							gl_compare::NONE, 0 );
		}
		
		static Sampler  Default3DAnisotropy (gl_wrap::type wrap = gl_wrap::CLAMP, uint anisotropy = 8)
		{
			return Sampler( Wrap( wrap, wrap, wrap ), Filter( gl_filter::LINEAR_MIPMAP_LINEAR, gl_filter::LINEAR ),
							gl_compare::NONE, anisotropy );
		}
	};

}	// Graphics
}	// Engine